Calling Conventions

cdecl (C/C++ calling convention)

  • .

  • cdecl only allows for 1 return value?

    • The cdecl calling convention does not inherently limit you to one return value.

    • The apparent “single return value” rule comes from C language semantics  and typical ABI usage, not from cdecl itself .

    • In C:

      • A function syntactically returns one expression.

      • Classic tutorials emphasize EAX.

    • But ABI-wise, that one expression can be:

      • a struct

      • a vector

      • written via hidden pointer

      • split across registers

Specification
  • Under classic 32-bit cdecl :

    • Integer/pointer return → EAX

    • Floating return → ST0 (x87)

    • Small aggregates → sometimes registers

    • Larger aggregates → hidden pointer (sret)

  • The convention defines how values are returned, not that only one logical value is allowed.

sysv (System V AMD64 ABI)

  • Aggregates (structs/unions) ≤ 16 bytes may be passed in registers (split into up to two 8-byte chunks).

  • Aggregates > 16 bytes are passed in memory.

  • Practically, this often behaves like pass-by-reference at the ABI boundary (because the object lives in memory), but technically it is still pass-by-value via memory.

win64 (Windows x64 calling convention)

  • Commonly referred to as:

    • win64

    • ms_abi  (LLVM/Clang attribute)

  • Aggregates > 8 bytes are passed by reference (hidden pointer).

  • Only 1, 2, 4, or 8-byte values are passed directly in registers.

Odin's "contextless"

  • It passes all parameters larger than 16 bytes by reference.

  • (2026-02-23)

  • "contextless"  vs "c"

    • Barinzaya:

      • Odin will implicitly pass large things by-pointer (since you can't modify proc arguments anyway), whereas C may not (because you can modify arguments in C).

      • In some cases, they may end up the same, but aren't in all cases.

      • It's still technically using the C ABI, it's mostly just passing pointers instead of values and inserting some implicit parameters (for context if applicable, and multiple return values). Different semantics, different ideals

      • So they're not compatible because things are different, but internally it's still just the C ABI with some changes to the parameters

    • avanspector:

      • registers are the same, the procedure prelude routine is not exactly the same

      • registers have to be the same in order to be compatible with native debuggers

  • "contextless"  vs "win64"

    • Caio:

      • "So it's more similar to how "win64" does it, then? but passing as a reference if above 16 bytes instead of 8 bytes?"

    • Barinzaya:

      • AFAIK they're still not quite the same, Win64 CC will make a copy on the stack and then pass a pointer to that, Odin CC will pass a pointer directly to the original

      • avanspector:

        • IIRC bill made a change some time ago to follow win64 cc in this case.

  • avanspector:

    • Odin's abi is not set in stone yet so I wouldn't rely on it long term.

  • rats:

    • i think most modern languages do something similar to odin

    • special internal calling convention unless you specify otherwise

    • zig has that @callconv(.c)  thing, as well as automatically applying it to all export functions i think

    • rust has #[repr(c)]  or whatever

stdcall

  • stdcall is a legacy 32-bit x86 calling convention primarily used on Windows. It standardizes stack cleanup and was widely used by the Win32 API.

  • Typical behavior:

    • Arguments passed on the stack (right to left)

    • Callee cleans the stack

    • Function names often decorated (e.g., _Func@8)

    • Return value in EAX (for integer/pointer)

    • This differs from cdecl, where the caller cleans the stack.

  • On x86-64

    • stdcall is effectively obsolete.

  • Reason: both major 64-bit ABIs use register-based calling and ignore legacy modifiers:

    • System V AMD64 ABI

    • Microsoft x64 calling convention

  • On 64-bit Windows (MSVC):

    • __stdcall  is accepted but ignored

    • All functions use the unified Microsoft x64 ABI

  • When stdcall still matters

    • Use it only when targeting 32-bit Windows and you must match an existing binary interface.

    • Common cases:

      • Win32 API functions

      • Old DLL exports

      • Legacy COM interfaces

      • Reverse engineering 32-bit binaries

  • Performance:

    • Historically:

      • Slightly smaller call sites than cdecl

      • Reduced stack adjustment instructions

    • In practice today:

      • Differences are negligible

      • Inlining and register ABIs dominate performance

      • Irrelevant on x86-64 and ARM64

fastcall

  • fastcall is a historical x86 (32-bit) calling convention designed to reduce call overhead by passing some arguments in registers instead of on the stack.

  • What fastcall means

    • Typical characteristics (32-bit x86):

    • First few arguments passed in registers (commonly ECX, EDX)

    • Remaining arguments on the stack

    • Callee usually cleans the stack

    • Intended to reduce memory traffic vs cdecl/stdcall

  • On x86-64

    • fastcall is essentially obsolete.

  • Reason: both major 64-bit ABIs already pass arguments in registers:

    • System V AMD64 ABI

    • Microsoft x64 calling convention

  • They are effectively “fastcall-like” by default, but standardized.

  • __fastcall  is ignored or treated as default on x64 MSVC

  • Most modern languages do not expose it on 64-bit targets

  • When fastcall still matters

    • Use it only if:

      • You are targeting 32-bit x86

      • You must match an existing binary interface

      • You are working with legacy Windows code

    • Common legacy cases:

      • Win32 APIs (some internal ones)

      • Old game engines

      • Reverse engineering

      • Binary hooking on x86

  • Performance

    • On modern systems:

      • Difference vs default x64 ABI: none

      • Difference vs optimized code with inlining: usually negligible

    • Biggest wins come from:

      • inlining

      • data layout

      • reducing call frequency